//! Top=level intermediate representation for passing to codegen backends.

use crate::identifier::IdentString;
use crate::ssa;
use crate::tysys::ValueType;

/// Codegen module with typedefs, roughly corresponding to a .stencil source
/// file.
pub struct Module {
    /// Toplevel name for the module.
    name: IdentString,

    /// Types definitions.
    tys: Vec<Type>,
}

impl Module {
    pub fn ident(&self) -> &IdentString {
        &self.name
    }

    pub fn tys(&self) -> &[Type] {
        &self.tys
    }
}

/// A type definition within a module.
pub enum Type {
    /// A basic buffer wrapper type.
    Buf(DataType),
}

impl Type {
    /// Returns the name of the type as an identifier.
    pub fn name(&self) -> &IdentString {
        match self {
            Self::Buf(t) => &t.name,
        }
    }
}

/// A serialized data container that's a thin wrapper around a buffer.
pub struct DataType {
    /// Unformatted name of the struct.
    name: IdentString,

    /// Possibly-enforced requirement on buffer.  This should be exposed in the
    /// constructor arguments if possible to avoid emitting run-time checks.
    length: Option<u64>,

    /// Implicit function checking for the buffer's well-formedness.
    form_check_thunk: Option<ssa::BufThunk>,

    /// Exposed functions for interacting with the type.
    fn_entries: Vec<Function>,
}

impl DataType {
    pub fn ident(&self) -> &IdentString {
        &self.name
    }

    pub fn length(&self) -> Option<u64> {
        self.length
    }

    pub fn form_check_thunk(&self) -> Option<&ssa::BufThunk> {
        self.form_check_thunk.as_ref()
    }

    pub fn fn_entries(&self) -> &[Function] {
        &self.fn_entries
    }
}

pub struct Function {
    name: IdentString,
    code: ssa::Function,
}

impl Function {
    pub fn new(name: IdentString, code: ssa::Function) -> Self {
        Self { name, code }
    }

    pub fn ident(&self) -> &IdentString {
        &self.name
    }

    pub fn code(&self) -> &ssa::Function {
        &self.code
    }

    /// If it mutates an underlying buffer.
    pub fn is_mutating(&self) -> bool {
        self.code.is_mutating()
    }

    pub fn return_ty(&self) -> &ValueType {
        self.code.return_ty()
    }
}

pub struct DataTypeBuilder {
    name: IdentString,
    exp_size: Option<u64>,
    fn_entries: Vec<Function>,
    check_thunk: Option<ssa::BufThunk>,
}

impl DataTypeBuilder {
    pub fn new(name: IdentString) -> Self {
        Self {
            name,
            exp_size: None,
            fn_entries: Vec::new(),
            check_thunk: None,
        }
    }

    /// Sets the buffer size hint.  This does not actually add a check, it's
    /// just a hint.
    pub fn set_size_hint(&mut self, size: u64) {
        self.exp_size = Some(size);
    }

    pub fn add_function(&mut self, fun: Function) {
        self.fn_entries.push(fun);
    }

    pub fn set_check_thunk(&mut self, th: ssa::BufThunk) {
        self.check_thunk = Some(th);
    }

    pub fn build(self) -> DataType {
        DataType {
            name: self.name,
            length: self.exp_size,
            form_check_thunk: self.check_thunk,
            fn_entries: self.fn_entries,
        }
    }
}

pub struct ModuleBuilder {
    name: IdentString,
    tys: Vec<Type>,
}

impl ModuleBuilder {
    pub fn new(name: IdentString) -> Self {
        Self {
            name,
            tys: Vec::new(),
        }
    }

    pub fn add_data_type(&mut self, dt: DataType) {
        self.tys.push(Type::Buf(dt));
    }

    pub fn build(self) -> Module {
        Module {
            name: self.name,
            tys: self.tys,
        }
    }
}
